home *** CD-ROM | disk | FTP | other *** search
/ MacFormat 1995 March / macformat-022.iso / Shareware City / Developers / src / out-of-phase-102-c / OutOfPhase 1.02 Source / OutOfPhase Folder / Level 0 Macintosh 29Sep94 / SoundOutput.c < prev    next >
Encoding:
C/C++ Source or Header  |  1994-11-23  |  13.0 KB  |  428 lines  |  [TEXT/KAHL]

  1. /* SoundOutput.c */
  2. /*****************************************************************************/
  3. /*                                                                           */
  4. /*    System Dependency Library for Building Portable Software               */
  5. /*    Macintosh Version                                                      */
  6. /*    Written by Thomas R. Lawrence, 1993 - 1994.                            */
  7. /*                                                                           */
  8. /*    This file is Public Domain; it may be used for any purpose whatsoever  */
  9. /*    without restriction.                                                   */
  10. /*                                                                           */
  11. /*    This package is distributed in the hope that it will be useful,        */
  12. /*    but WITHOUT ANY WARRANTY; without even the implied warranty of         */
  13. /*    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.                   */
  14. /*                                                                           */
  15. /*    Thomas R. Lawrence can be reached at tomlaw@world.std.com.             */
  16. /*                                                                           */
  17. /*****************************************************************************/
  18.  
  19. #include "MiscInfo.h"
  20. #include "Debug.h"
  21. #include "Audit.h"
  22. #include "Definitions.h"
  23.  
  24. #ifdef THINK_C
  25.     #pragma options(pack_enums)
  26. #endif
  27. #include <Sound.h>
  28. #include <SANE.h>
  29. #include <Errors.h>
  30. #ifdef THINK_C
  31.     #pragma options(!pack_enums)
  32. #endif
  33.  
  34. #include "SoundOutput.h"
  35. #include "Memory.h"
  36.  
  37.  
  38. #define MinimumInitialNumberOfBuffers (3)
  39.  
  40. typedef struct MyStructure
  41.     {
  42.         struct MyStructure*        Next;
  43.         struct MyStructure*        Previous;
  44.         SndCommand                        MySoundCommand;
  45.         SndCommand                        MyCallbackCommand;
  46.         volatile MyBoolean        InUseFlag; /* interrupt level flag; hence "volatile" */
  47.         ExtSoundHeader                Header;
  48.         char*                                    SampleArea;
  49.     } MyStructure;
  50.  
  51.  
  52. static MyBoolean            SoundSystemInUse = False;
  53.  
  54. static MyStructure*        NextAvailableBuffer;
  55. static MyBoolean            IsCurrentBufferCheckedOut;
  56.  
  57. static long                        MaxFramesPerBuffer;
  58. static int                        BytesPerFrame;
  59. static int                        MaxBuffersToAllocate;
  60. static int                        CurrentBufferCount;
  61. static long                        TheSamplingRate;
  62. static MyBoolean            StereoFlag;
  63. static MyBoolean            SixteenBitFlag;
  64.  
  65. static SndChannel*        MySoundChannel;
  66.  
  67.  
  68. static void                            AllocateANewBuffer(void);
  69. static void                            DisposeBuffers(void);
  70. static pascal void            MyCallBack(SndChannel* Channel, SndCommand* Command);
  71.  
  72.  
  73. /* attempt to obtain a sound channel.  returns True if the sound channel was opened */
  74. /* or False if it couldn't be (if already in use or machine doesn't support sound) */
  75. MyBoolean        OpenSoundChannel(long SamplingRate, SoundOutputStereo WantStereo,
  76.                             SoundOutputNumBits NumBits, long FramesPerBuffer, int MaxNumBuffers,
  77.                             int InitialNumBuffers)
  78.     {
  79.         OSErr                            Error;
  80.         SndCommand                Cmd;
  81.         long                            InitOptions;
  82.  
  83.         /* open the sound channel */
  84.         if (SoundSystemInUse)
  85.             {
  86.                 return False;
  87.             }
  88.         InitOptions = initNoInterp /* | initNoDrop */;
  89.         if (WantStereo == eStereo)
  90.             {
  91.                 InitOptions |= initStereo;
  92.             }
  93.         MySoundChannel = NIL;
  94.         Error = SndNewChannel(&MySoundChannel,sampledSynth,InitOptions,
  95.             (SndCallBackProcPtr)&MyCallBack);
  96.         if (Error == noErr)
  97.             {
  98.                 /* we want maximum volume on the sound channel */
  99.                 Cmd.cmd = ampCmd;
  100.                 Cmd.param1 = 255;
  101.                 Cmd.param2 = 0;
  102.                 Error = SndDoImmediate(MySoundChannel,&Cmd);
  103.                 if (Error == noErr)
  104.                     {
  105.                         /* initialize the variables */
  106.                         TheSamplingRate = SamplingRate;
  107.                         StereoFlag = (WantStereo == eStereo);
  108.                         SixteenBitFlag = (NumBits == e16bit);
  109.                         NextAvailableBuffer = NIL;
  110.                         MaxFramesPerBuffer = FramesPerBuffer;
  111.                         BytesPerFrame = 1;
  112.                         if (StereoFlag)
  113.                             {
  114.                                 BytesPerFrame *= 2; /* double the number of words per sample frame */
  115.                             }
  116.                         if (SixteenBitFlag)
  117.                             {
  118.                                 BytesPerFrame *= (sizeof(short) / sizeof(char)); /* words, not bytes */
  119.                             }
  120.                         MaxBuffersToAllocate = MaxNumBuffers;
  121.                         CurrentBufferCount = 0;
  122.                         IsCurrentBufferCheckedOut = False;
  123.                         /* allocate the buffers that were requested to begin with */
  124.                         if (InitialNumBuffers < MinimumInitialNumberOfBuffers)
  125.                             {
  126.                                 InitialNumBuffers = MinimumInitialNumberOfBuffers;
  127.                             }
  128.                         while (InitialNumBuffers > 0)
  129.                             {
  130.                                 AllocateANewBuffer();
  131.                                 InitialNumBuffers -= 1;
  132.                             }
  133.                         /* notice that we escape here */
  134.                         if (CurrentBufferCount > 1)
  135.                             {
  136.                                 /* we need at least 2 buffers, otherwise the sound will skip so */
  137.                                 /* there'd be no point in doing it with 1 buffer.  Other systems */
  138.                                 /* may not have this restriction (e.g. UNIX) */
  139.                                 SoundSystemInUse = True;
  140.                                 return True;
  141.                             }
  142.                         DisposeBuffers();
  143.                     }
  144.                 SndDisposeChannel(MySoundChannel,True);
  145.             }
  146.         return False;
  147.     }
  148.  
  149.  
  150. /* internal routine to add a buffer to the existing ring of buffers. */
  151. static void                AllocateANewBuffer(void)
  152.     {
  153.         MyStructure*        Buffer;
  154.         long double            Fred;
  155.  
  156.         Buffer = (MyStructure*)AllocPtrCanFail(sizeof(MyStructure),"SoundBufHeader");
  157.         if (Buffer != NIL)
  158.             {
  159.                 Buffer->SampleArea = AllocPtrCanFail(MaxFramesPerBuffer
  160.                     * BytesPerFrame,"SndBuffer");
  161.                 if (Buffer->SampleArea == NIL)
  162.                     {
  163.                         ReleasePtr((char*)Buffer);
  164.                         return;
  165.                     }
  166.                 Buffer->InUseFlag = False;
  167.                 Buffer->Header.samplePtr = Buffer->SampleArea;
  168.                 if (SixteenBitFlag)
  169.                     {
  170.                         Buffer->Header.sampleSize = 16;
  171.                     }
  172.                  else
  173.                     {
  174.                         Buffer->Header.sampleSize = 8;
  175.                     }
  176.                 if (StereoFlag)
  177.                     {
  178.                         Buffer->Header.numChannels = 2;
  179.                     }
  180.                  else
  181.                     {
  182.                         Buffer->Header.numChannels = 1;
  183.                     }
  184.                 Buffer->Header.sampleRate = (unsigned long)TheSamplingRate << 16;
  185.                 Buffer->Header.loopStart = 0;
  186.                 Buffer->Header.loopEnd = 0;
  187.                 Buffer->Header.encode = extSH;
  188.                 Buffer->Header.baseFrequency = 64;
  189.                 Buffer->Header.markerChunk = NULL;
  190.                 Buffer->Header.futureUse1 = 0;
  191.                 Buffer->Header.futureUse2 = 0;
  192.                 Buffer->Header.futureUse3 = 0;
  193.                 Buffer->Header.futureUse4 = 0;
  194.                 Fred = TheSamplingRate;
  195.                 x96tox80(&Fred,&(Buffer->Header.AIFFSampleRate)); /* extended type */
  196.                 /* now, link the buffer */
  197.                 if (NextAvailableBuffer != NIL)
  198.                     {
  199.                         /* link this block in */
  200.                         Buffer->Next = NextAvailableBuffer;
  201.                         Buffer->Previous = NextAvailableBuffer->Previous;
  202.                         /* link enclosing buffers to it */
  203.                         NextAvailableBuffer->Previous->Next = Buffer;
  204.                         NextAvailableBuffer->Previous = Buffer;
  205.                         /* stick it where we can use it */
  206.                         NextAvailableBuffer = Buffer;
  207.                     }
  208.                  else
  209.                     {
  210.                         Buffer->Next = Buffer;
  211.                         Buffer->Previous = Buffer;
  212.                         NextAvailableBuffer = Buffer;
  213.                     }
  214.                 CurrentBufferCount += 1;
  215.             }
  216.     }
  217.  
  218.  
  219. /* internal routine to dispose of all of the buffers */
  220. /* don't call this unless they're InUseFlag is clear!!! */
  221. static void                DisposeBuffers(void)
  222.     {
  223.         while (NextAvailableBuffer != NIL)
  224.             {
  225.                 MyStructure*        Temp;
  226.  
  227.                 Temp = NextAvailableBuffer;
  228.                 if (NextAvailableBuffer->Next == NextAvailableBuffer)
  229.                     {
  230.                         /* degenerate */
  231.                         NextAvailableBuffer = NIL;
  232.                     }
  233.                  else
  234.                     {
  235.                         NextAvailableBuffer->Previous->Next = NextAvailableBuffer->Next;
  236.                         NextAvailableBuffer->Next->Previous = NextAvailableBuffer->Previous;
  237.                         NextAvailableBuffer = NextAvailableBuffer->Next;
  238.                     }
  239.                 ReleasePtr(Temp->SampleArea);
  240.                 ReleasePtr((char*)Temp);
  241.             }
  242.     }
  243.  
  244.  
  245. #ifdef THINK_C
  246.     #if __option(profile)
  247.         #define Profiling (True)
  248.     #else
  249.         #define Profiling (False)
  250.     #endif
  251.  
  252.     #pragma options(!profile)
  253. #endif
  254.  
  255. /* asynchronous callback routine which marks buffers as now unused */
  256. /* It would be very bad to profile this since profiling tampers with the */
  257. /* stack invocation and uses A5 global variables!  (believe me, I've tried) */
  258. static pascal void    MyCallBack(SndChannel* Channel, SndCommand* Command)
  259.   {
  260.     *(MyBoolean*)(Command->param2) = 0;
  261.   }
  262.  
  263. #ifdef THINK_C
  264.     #if Profiling
  265.         #pragma options(profile)
  266.     #endif
  267. #endif
  268.  
  269.  
  270. /* close the sound channel and clean up the buffers */
  271. /* waits until all buffers have played out */
  272. void                CloseSoundChannel(void (*Callback)(void* Refcon), void* Refcon)
  273.     {
  274.         MyStructure*        Scan;
  275.  
  276.         ERROR(!SoundSystemInUse,PRERR(ForceAbort,
  277.             "CloseSoundChannel called, but sound channel isn't open"));
  278.         SoundSystemInUse = False;
  279.      LoopPoint:
  280.         Scan = NextAvailableBuffer;
  281.         do
  282.             {
  283.                 if (Scan->InUseFlag)
  284.                     {
  285.                         if (Callback != NIL)
  286.                             {
  287.                                 (*Callback)(Refcon);
  288.                             }
  289.                         goto LoopPoint;
  290.                     }
  291.                 Scan = Scan->Next;
  292.             } while (Scan != NextAvailableBuffer /* "Full Circle" */);
  293.         /* dispose of the blasted thing */
  294.         SndDisposeChannel(MySoundChannel,True/*shutupnow*/);
  295.         /* release the memory */
  296.         DisposeBuffers();
  297.     }
  298.  
  299.  
  300. /* obtain a pointer to one of the [nonrelocatable] sound buffers.  Data in this */
  301. /* buffer is interpreted as such:  For 8-bit mono, the buffer is an array of */
  302. /* signed chars.  For 16bit mono, the buffer is an array of 2-byte signed integers in */
  303. /* the machine's native endianness.  For 8-bit stereo, the buffer is an array of */
  304. /* 2-byte tuples; the byte lower in memory is the left channel.  For 16-bit stereo, */
  305. /* the buffer is an array of 2-(2-byte) tuples, the left channel is lower in memory. */
  306. /* If there are no buffers currently available (and new ones couldn't be allocated) */
  307. /* then it returns NIL */
  308. char*                CheckOutSoundBuffer(void)
  309.     {
  310.         ERROR(!SoundSystemInUse,PRERR(ForceAbort,
  311.             "CheckOutSoundBuffer called, but sound channel isn't open"));
  312.         ERROR(IsCurrentBufferCheckedOut,PRERR(ForceAbort,
  313.             "CheckOutSoundBuffer called while a buffer has already been checked out"));
  314.         if (NextAvailableBuffer->InUseFlag)
  315.             {
  316.                 /* oops, buffer is in use, try to make another */
  317.                 if (CurrentBufferCount < MaxBuffersToAllocate)
  318.                     {
  319.                         AllocateANewBuffer();
  320.                     }
  321.                 if (NextAvailableBuffer->InUseFlag)
  322.                     {
  323.                         return NIL;
  324.                     }
  325.             }
  326.         IsCurrentBufferCheckedOut = True;
  327.         return NextAvailableBuffer->SampleArea;
  328.     }
  329.  
  330.  
  331. /* submit a buffer to be queued to the system's sound channel.  The number of frames */
  332. /* in the buffer actually used is specified to allow less than the full buffer to */
  333. /* be used. */
  334. void                SubmitBuffer(char* Buffer, long NumUsedFrames,
  335.                             void (*Callback)(void* Refcon), void* Refcon)
  336.     {
  337.         OSErr            Error;
  338.         long            Scan;
  339.  
  340.         ERROR(!SoundSystemInUse,PRERR(ForceAbort,
  341.             "SubmitBuffer called, but sound channel isn't open"));
  342.         ERROR(!IsCurrentBufferCheckedOut,PRERR(ForceAbort,
  343.             "SubmitBuffer called but no buffer has been checked out"));
  344.         ERROR(Buffer != NextAvailableBuffer->SampleArea,
  345.             PRERR(ForceAbort,"SubmitBuffer:  Wrong buffer was submitted"));
  346.         ERROR((NumUsedFrames < 0) || (NumUsedFrames > MaxFramesPerBuffer),
  347.             PRERR(ForceAbort,"SubmitBuffer:  Number of used frames exceeds buffer size!"));
  348.         IsCurrentBufferCheckedOut = False;
  349.         ERROR(NextAvailableBuffer->InUseFlag,PRERR(ForceAbort,
  350.             "SubmitBuffer:  Internal error -- InUseFlag is set but shouldn't be"));
  351.         /* adjust the signed samples to be unsigned */
  352.         /* due to Apple's silliness, this only needs to be done for 8-bit samples */
  353.         if (StereoFlag)
  354.             {
  355.                 if (!SixteenBitFlag)
  356.                     {
  357.                         /* stereo, 8-bit */
  358.                         for (Scan = (2 * NumUsedFrames) - 1; Scan >= 0; Scan -= 1)
  359.                             {
  360.                                 ((char*)Buffer)[Scan] += 0x80;
  361.                             }
  362.                     }
  363.             }
  364.          else
  365.             {
  366.                 if (!SixteenBitFlag)
  367.                     {
  368.                         /* mono, 8-bit */
  369.                         for (Scan = NumUsedFrames - 1; Scan >= 0; Scan -= 1)
  370.                             {
  371.                                 ((char*)Buffer)[Scan] += 0x80;
  372.                             }
  373.                     }
  374.             }
  375.         /* submit the command to actually play the buffer */
  376.      TryAgainPoint1:
  377.         NextAvailableBuffer->InUseFlag = True;
  378.         NextAvailableBuffer->MySoundCommand.cmd = bufferCmd;
  379.         NextAvailableBuffer->MySoundCommand.param1 = 0;
  380.         NextAvailableBuffer->MySoundCommand.param2 = (long)&(NextAvailableBuffer->Header);
  381.         NextAvailableBuffer->Header.numFrames = NumUsedFrames;
  382.         Error = SndDoCommand(MySoundChannel,&(NextAvailableBuffer->MySoundCommand),True);
  383.         if (queueFull == Error)
  384.             {
  385.                 /* oops, we filled up the OS queue; wait a little and try again */
  386.                 if (Callback != NIL)
  387.                     {
  388.                         (*Callback)(Refcon);
  389.                     }
  390.                 goto TryAgainPoint1;
  391.             }
  392.         /* submit the callback routine request.  the callback is an interrupt level */
  393.         /* thing that clears a buffer so we can use it again. */
  394.      TryAgainPoint2:
  395.         NextAvailableBuffer->MyCallbackCommand.cmd = callBackCmd;
  396.         /* say where to store the "0" */
  397.         NextAvailableBuffer->MyCallbackCommand.param2
  398.             = (long)&(NextAvailableBuffer->InUseFlag);
  399.         Error = SndDoCommand(MySoundChannel,&(NextAvailableBuffer->MyCallbackCommand),True);
  400.         if (queueFull == Error)
  401.             {
  402.                 if (Callback != NIL)
  403.                     {
  404.                         (*Callback)(Refcon);
  405.                     }
  406.                 goto TryAgainPoint2;
  407.             }
  408.         /* advance to the next buffer */
  409.         NextAvailableBuffer = NextAvailableBuffer->Next;
  410.     }
  411.  
  412.  
  413. /* discard all queued data on the sound channel and close it immediately */
  414. void                KillSoundChannel(void)
  415.     {
  416.         ERROR(!SoundSystemInUse,PRERR(ForceAbort,
  417.             "KillSoundChannel:  but sound channel isn't open"));
  418.  
  419.         /* dispose of the sound channel */
  420.         SndDisposeChannel(MySoundChannel,True/*shutupnow*/);
  421.  
  422.         /* release buffers */
  423.         DisposeBuffers();
  424.  
  425.         /* mark sound subsystem as available */
  426.         SoundSystemInUse = False;
  427.     }
  428.